#include <stdlib.h>
#include <string.h>
#include <config.h>
#include <stm32f0xx.h>
#include <device.h>
#include <printk.h>
#include "stm32_vcom.h"
#include "board.h"

#ifdef USING_VCOM

/* STM32 vcom driver */
struct vcom_data
{
    uint16_t bit;
    uint16_t data;
    int8_t bitcnt;
};
struct vcom_hw
{
    GPIO_TypeDef *port;
    uint16_t      pin;

    uint16_t it_cc;

    void (*set_capture_mode)(TIM_TypeDef *);
    void (*set_compare_mode)(TIM_TypeDef *);

    uint16_t (*get_capture)(TIM_TypeDef *);
    void (*set_compare)(TIM_TypeDef *, uint16_t val);
};

struct stm32_vcom
{
    TIM_TypeDef *tim_dev;
    IRQn_Type irq;

    struct vcom_hw   vh_tx, vh_rx;
    struct vcom_data vd_tx, vd_rx;

    uint8_t databitcnt;
    int bit_interval;
};
#if 0
static void TIM_chn1_capture(TIM_TypeDef *TIMx)
{
    Var_P_BB((int) & (TIMx->CCER), 0) = 0;
    TIMx->CCMR1 ^= (u16)(0x01) | (u16)(0x0E << 4);
    Var_P_BB((int) & (TIMx->CCER), 0) = 1;
}
static void TIM_chn2_capture(TIM_TypeDef *TIMx)
{
    Var_P_BB((int) & (TIMx->CCER), 4) = 0;
    TIMx->CCMR1 ^= (u16)(0x01 << 8) | (u16)(5 << 12);
    Var_P_BB((int) & (TIMx->CCER), 4) = 1;
}
#endif
static void TIM_chn3_capture(TIM_TypeDef *TIMx)
{
    Var_P_BB((int) & (TIMx->CCER), 8) = 0;
    TIMx->CCMR2 ^= (u16)(0x01) | (u16)(5 << 4);
    Var_P_BB((int) & (TIMx->CCER), 8) = 1;
}
static void TIM_chn4_capture(TIM_TypeDef *TIMx)
{
    Var_P_BB((int) & (TIMx->CCER), 12) = 0;
    TIMx->CCMR2 ^= (u16)(0x01 << 8) | (u16)(9 << 12);
    Var_P_BB((int) & (TIMx->CCER), 12) = 1;
}
#if 0
static void TIM_chn1_compare(TIM_TypeDef *TIMx)
{
    Var_P_BB((int) & (TIMx->CCER), 0) = 0;
    TIMx->CCMR1 &= 0xFF00;
}

static void TIM_chn2_compare(TIM_TypeDef *TIMx)
{
    Var_P_BB((int) & (TIMx->CCER), 4) = 0;
    TIMx->CCMR1 &= 0x00FF;
}
#endif
static void TIM_chn3_compare(TIM_TypeDef *TIMx)
{
    Var_P_BB((int) & (TIMx->CCER), 8) = 0;
    TIMx->CCMR2 &= 0xFF00;
}
static void TIM_chn4_compare(TIM_TypeDef *TIMx)
{
    Var_P_BB((int) & (TIMx->CCER), 12) = 0;
    TIMx->CCMR2 &= 0x00FF;
}


static err_t stm32_vcom_cfg(struct serial_device *serial, struct serial_configure *cfg)
{
    struct stm32_vcom *vcom;

    vcom = (struct stm32_vcom *)serial->parent.user_data;

    vcom->databitcnt = 1;
    switch (cfg->data_bits)
    {
    case DATA_BITS_8:
        vcom->databitcnt += 8;
        break;
    case DATA_BITS_9:
        vcom->databitcnt += 9;
        break;
    }

    switch (cfg->stop_bits)
    {
    case STOP_BITS_1:
        vcom->databitcnt += 1;
        break;
    case STOP_BITS_2:
        vcom->databitcnt += 2;
        break;
    }

    vcom->bit_interval = SYS_CLOCK / cfg->baud_rate;

    return 0;
}

static err_t stm32_vcom_ctrl(struct serial_device *serial, int cmd, void *arg)
{
    struct stm32_vcom *vcom;

    vcom = (struct stm32_vcom *)serial->parent.user_data;
    switch (cmd)
    {
    case DEVICE_CTRL_CLR_INT:
        TIM_ITConfig(vcom->tim_dev, vcom->vh_rx.it_cc, DISABLE);
        break;
    case DEVICE_CTRL_SET_INT:
        TIM_ITConfig(vcom->tim_dev, vcom->vh_rx.it_cc, ENABLE);
        break;
    case DEVICE_CTRL_CLR_TX_INT:
        TIM_ITConfig(vcom->tim_dev, vcom->vh_tx.it_cc, DISABLE);
        break;
    case DEVICE_CTRL_SET_TX_INT:
        TIM_ITConfig(vcom->tim_dev, vcom->vh_tx.it_cc, ENABLE);
        break;
    }
    return 0;
}

static uint16_t check_parity(struct serial_configure *cfg, uint8_t c)
{
    uint16_t d = c;

    switch (cfg->parity)
    {
    case PARITY_NONE:
        d |= 1 << 8;
        break;
    case PARITY_ODD :
        if (!(count_bit_in_long(c) & 0x01)) d |= 1 << 8;
        d |= 1 << 9;
        break;
    case PARITY_EVEN:
        if ((count_bit_in_long(c) & 0x01))  d |= 1 << 8;
        d |= 1 << 9;
        break;
    }

    return d;
}

static int stm32_vcom_putc(struct serial_device *serial, char c)
{
    uint16_t d;
    struct stm32_vcom *vcom = (struct stm32_vcom *)serial->parent.user_data;

    d = check_parity(&serial->config, c);

    d <<= 0x01; // Add start bit
    d &= (1 << vcom->databitcnt) - 1;
    vcom->vd_tx.data = d;

    vcom->vd_tx.bitcnt = vcom->databitcnt;
    vcom->vd_tx.bit    = 0x01;

    return 1;
}
static int stm32_vcom_getc(struct serial_device *serial)
{
    int ch;
    struct stm32_vcom *vcom;

    vcom = (struct stm32_vcom *)serial->parent.user_data;

    ch = -1;
    if (vcom->vd_rx.bitcnt == vcom->databitcnt - 1)
    {
        uint16_t d;

        ch = get_bits(vcom->vd_rx.data, 0, 7);   // remove stop bit
        d = check_parity(&serial->config, ch);
        if (d != vcom->vd_rx.data)
        {
            //parity error
        }

        vcom->vd_rx.bitcnt = 0;
    }

    return ch;
}

/**
 * Uart common interrupt process. This need add to uart ISR.
 *
 * @param serial serial device
 */
static void stm32_vcom_isr(struct serial_device *serial)
{
    uint16_t capture;
    struct vcom_data *vd;
    struct vcom_hw   *vh;

    struct stm32_vcom *vcom = (struct stm32_vcom *)serial->parent.user_data;

    /* vcom rx */
    vh = &vcom->vh_rx;
    if (TIM_GetITStatus(vcom->tim_dev, vh->it_cc))
    {
        TIM_ClearITPendingBit(vcom->tim_dev, vh->it_cc);
        vd = &vcom->vd_rx;

        capture = vh->get_capture(vcom->tim_dev);
        if (vd->bitcnt == 0)
        {
            capture += vcom->bit_interval >> 2;
            capture += vcom->bit_interval;
            vh->set_compare_mode(vcom->tim_dev);
            vh->set_compare(vcom->tim_dev, capture);
            vd->bitcnt = 1;
            vd->data   = 0x00;
            vd->bit    = 0x01;
        }
        else if (vd->bitcnt < vcom->databitcnt - 1)
        {
            if (GPIO_ReadInputDataBit(vh->port, vh->pin))
                vd->data |= vd->bit;
            vd->bit <<= 0x01;
            vd->bitcnt++;
            capture += vcom->bit_interval;
            vh->set_compare(vcom->tim_dev, capture);
        }
        else
        {
            if (GPIO_ReadInputDataBit(vh->port, vh->pin))
                vd->data |= vd->bit;
            vh->set_capture_mode(vcom->tim_dev);
            serial_device_isr(serial, SERIAL_EVENT_RX_IND);
        }
    }

    /* SWIM tx */
    vh = &vcom->vh_tx;
    if (TIM_GetITStatus(vcom->tim_dev, vh->it_cc))
    {
        TIM_ClearITPendingBit(vcom->tim_dev, vh->it_cc);
        vd = &vcom->vd_tx;

        capture = vh->get_capture(vcom->tim_dev);
        capture += vcom->bit_interval;
        vh->set_compare(vcom->tim_dev, capture);

        if (vd->bitcnt-- > 0)
        {
            if (vd->data & vd->bit)
            {
                GPIO_SetBits(vh->port, vh->pin);
            }
            else
            {
                GPIO_ResetBits(vh->port, vh->pin);
            }
            vd->bit <<= 0x01;
        }
        else
        {
            GPIO_SetBits(vh->port, vh->pin);
            serial_device_isr(serial, SERIAL_EVENT_TX_RDY);
        }
    }
}

#if defined(USING_VCOM)
/* VCOM device driver structure */

struct stm32_vcom stm32_vcom1 =
{
    .tim_dev = TIM4,
    .irq     = TIM4_IRQn,
    .vh_rx = {
        .port = GPIOB, .pin = GPIO_Pin_8, .it_cc = TIM_IT_CC3,
        .set_capture_mode = TIM_chn3_capture, .set_compare_mode = TIM_chn3_compare,
        .get_capture = TIM_GetCapture3, .set_compare = TIM_SetCompare3,
    },
    .vh_tx = {
        .port = GPIOB, .pin = GPIO_Pin_9, .it_cc = TIM_IT_CC4,
        .set_capture_mode = TIM_chn4_capture, .set_compare_mode = TIM_chn4_compare,
        .get_capture = TIM_GetCapture4, .set_compare = TIM_SetCompare4,
    },
};
struct serial_device vcom1;
void TIM4_IRQHandler(void)
{
    stm32_vcom_isr(&vcom1);
}
#endif

static void RCC_Configuration(void)
{
#if defined(USING_VCOM)
    /* Enable VCOM GPIO clocks */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);
    /* Enable TIM clock */
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
#endif
}

static void GPIO_Configuration(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;

    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;

#if defined(USING_VCOM)
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOB, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
    GPIO_Init(GPIOB, &GPIO_InitStructure);

    GPIO_SetBits(GPIOB, GPIO_Pin_9);

    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    TIM_TimeBaseStructure.TIM_Period = 0xffff;
    TIM_TimeBaseStructure.TIM_Prescaler = 0;
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV2;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);

    Var_P_BB((int) & (TIM4->CCER), 9) = 1; // Falling edge

    TIM_chn3_capture(TIM4);
    TIM_chn4_compare(TIM4);
#endif

    TIM_Cmd(TIM4, ENABLE);
}

static void NVIC_Configuration(struct stm32_vcom *vcom)
{
    NVIC_EnableIRQ(vcom->irq);
}


static const struct uart_ops vcom_uart_ops =
{
    stm32_vcom_cfg,
    stm32_vcom_ctrl,
    stm32_vcom_putc,
    stm32_vcom_getc,
};

void board_vcom_init(void)
{
    struct stm32_vcom *vcom;
    struct serial_configure config = SERIAL_CONFIG_DEFAULT;

    RCC_Configuration();
    GPIO_Configuration();

#ifdef USING_VCOM
    vcom = &stm32_vcom1;
    config.baud_rate = BAUD_RATE_2400;
    config.bufsz     = 0x100;
    vcom1.ops      = &vcom_uart_ops;
    vcom1.config   = config;

    NVIC_Configuration(vcom);

    serial_device_register(&vcom1, "vcom1", 0, vcom);
#endif
}
#endif

